package com.eerl.pdev.werl.impl;

import com.eerl.pdev.util.EToolPluginConsoleToolWindowFactory;
import com.eerl.pdev.util.Notice;
import com.eerl.pdev.werl.ERpc;
import com.eerl.pdev.werl.ERpcReturn;
import com.eerl.pdev.werl.MsgHandler;
import com.ericsson.otp.erlang.*;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.project.Project;
import lombok.Getter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
// SSSSSSSSSSSSSSSSSSSSSS!BBBBBBBBBBBBBBBBBBBBBBBBBBB!
public class ErlangNode extends Thread implements ERpc<ErlangType> {

    public static final String CALL_RPC_FLAG = "call1730289556000call" ;
    private Project project;
    private final String cookie;
    private final String remoteHost;

    @Getter
    private final static String MAIL_BOX_NAME = "xllmailbox1729775080000xllmailbox";

    @Getter
    private final static String SELF_NODE_NAME = "xllxyy_java_node@127.0.0.1";

    private final static String ERLANG_MFA_NODE_NAME = "rex";

    private final static int STATE_NONE = 0;
    private final static int STATE_RUNNING = 1;

    private OtpNode node;
    private int state = STATE_NONE;
    private OtpMbox mbox;
    private Map<String, MsgHandler> msgHandlerMap = new HashMap<>();
    private Map<Integer, Integer> nodeState = new HashMap<>();
    private MsgHandler defaultHandle;

    // not allow too many request
    private BlockingQueue<OtpErlangTuple> callReturnMsgQueue = new SynchronousQueue<>();
    private volatile boolean isCalling;

    private int callTimeoutMillion = 2000;

    public ErlangNode(Project project, String cookie, String remoteHost) {
        this.project = project;
        this.cookie = cookie;
        this.remoteHost = remoteHost;
        initRemoteNodeConfiguration();
    }

    @Override
    public void initRemoteNodeConfiguration() {
        try {
            node = new OtpNode(SELF_NODE_NAME, cookie);
            mbox = node.createMbox(MAIL_BOX_NAME);
            defaultHandle = new DefaultMsgHandler(project);
            state = STATE_RUNNING;
            start();
        } catch (IOException e) {
            Notice.showNotification(project, "create erlang node fail, err: " + e.getMessage(), NotificationType.ERROR);
            EToolPluginConsoleToolWindowFactory.debugPrint(project, e.getLocalizedMessage());
            EToolPluginConsoleToolWindowFactory.debugPrint(project, e.fillInStackTrace().toString());
            EToolPluginConsoleToolWindowFactory.debugPrint(project, e.toString());
        }
    }

    private void handleMsg(String matchCase, OtpErlangTuple args) {
        // deal msg and update nodeState
        msgHandlerMap.getOrDefault(matchCase, defaultHandle).handle(nodeState, args);
    }

    @Override
    public void run() {
        while (state == STATE_RUNNING) {
            try {
                OtpErlangObject receive = mbox.receive();
                EToolPluginConsoleToolWindowFactory.printToConsole(project, receive.toString());
                System.out.println(receive.getClass());
                if (receive instanceof OtpErlangTuple msg) {
                    OtpErlangObject matchCaseObj = msg.elementAt(0);
                    System.out.println(matchCaseObj + "|" + matchCaseObj.toString().equals("rex"));
                    if (isCalling && CALL_RPC_FLAG.equals(matchCaseObj.toString())) {
                        // call msg
                        callReturnMsgQueue.put(msg);
                        continue;
                    } else if (ERLANG_MFA_NODE_NAME.equals(matchCaseObj.toString())) {
                        callReturnMsgQueue.put(msg);
                        continue;
                    }
                    handleMsg(matchCaseObj.toString(), msg);
                } else {
                    Notice.showNotification(project, "错误消息无法处理...." + receive, NotificationType.ERROR);
                    EToolPluginConsoleToolWindowFactory.debugPrint(project, receive.toString());
                }
            } catch (OtpErlangExit | OtpErlangDecodeException | InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    ERpcReturn<ErlangType> castToERpcReturn(OtpErlangTuple msg, ErlangType[] constructParseEnumList) {
        if (ERLANG_MFA_NODE_NAME.equals(msg.elementAt(0).toString())) {
            System.out.println("debug --------> 1" + msg);
            return new OtpObjectRpcReturn(true, "null", "", new ErlangType(msg.elementAt(1).toString(), ErlangType.ERLANG_TYPE_STRING));
        }
        System.out.println("debug --------> 2" + msg);
        // OtpErlangObject _handerCaseMatch = msg.elementAt(0);
        boolean isSuccess = Boolean.parseBoolean(msg.elementAt(1).toString());
        String caseMatch = msg.elementAt(2).toString();
        String errMsg = msg.elementAt(3).toString();
        ErlangType data =  ErlTypeCast.toErlangType(msg.elementAt(4), constructParseEnumList);
        return new OtpObjectRpcReturn(isSuccess, caseMatch, errMsg, data);
    }

    // ------------------------- public -----------------------------
    /**
     * add msg handler, if not add, will have one default handler to deal msg, it will print error msg direct into console
     */
    public void addMsgHandler(MsgHandler handler) {
        msgHandlerMap.put(handler.getMatchCase(), handler);
    }


    @Override
    public void cast(String mod, String fun, ErlangType[] args) {
        // mbox will receive {rex, Data}
        mbox.send(ERLANG_MFA_NODE_NAME, remoteHost,
                new OtpErlangTuple(new OtpErlangObject[]{
                        mbox.self(),
                        new OtpErlangTuple(
                                new OtpErlangObject[] {
                                        new OtpErlangAtom("call"),
                                        new OtpErlangAtom(mod),
                                        new OtpErlangAtom(fun),
                                        ErlTypeCast.castToList(args),
                                        new OtpErlangAtom("user")
                                }
                        )

                })
        );
    }

    /**
     * no realized
     */
    @Override
    public ERpcReturn<ErlangType> call(String mod, String fun, ErlangType[] args, ErlangType[] msgParseEnumList) {
        EToolPluginConsoleToolWindowFactory.printToConsole(project, String.valueOf(System.nanoTime()));
        isCalling = true;
        cast(mod, fun, args);
        try {
            OtpErlangTuple msg = callReturnMsgQueue.poll(callTimeoutMillion, TimeUnit.MILLISECONDS);
            if (msg == null) {
                EToolPluginConsoleToolWindowFactory.printToConsole(project, String.valueOf(System.nanoTime()));
                throw new RuntimeException("maby call timeout...");
            }
            return castToERpcReturn(msg, msgParseEnumList);
        } catch (InterruptedException e) {
            throw new RuntimeException("maby call timeout... or interrupted", e);
        } finally {
            isCalling = false;
        }
    }
}
